import SdkCommand from "../command/sdk.command"
import sweepAlignmentEnum from "../enum/sweepAlignment.enum"
import sweepEventEnum from "../enum/sweepEvent.enum"
import sweepPlacementEnum from "../enum/sweepPlacement.enum"
import transitionEnum from "../enum/transition.enum"
import { AggregateSubscription, SdkCollection, SdkObservable } from "./observable"
import { ClassFactory, copyPosition, deepDiffers, isNum, isSamePoint } from "./utils"
const config = Object.freeze({
  signedUrlDefaultExpireTime: 240000,
  signedUrlCheckInterval: 10000,
  signedUrlRefreshBuffer: 15000,
  visionTilingStartDate: new Date("8/26/2016"),
  visionTilingStartVersion: "1.1.407.13667",
  epsilon: 0.00001,
  skyboxMeshGroup: "_group_skybox_",
  skysphereMeshGroup: "_group_skysphere_"
})
function isString(e) {
  return !!e && "string" == typeof e
}
function isObject(e) {
  return !!e && "object" == typeof e
}
function isOrientation(e) {
  if (e && "object" == typeof e && "yaw" in e && "pitch" in e && "roll" in e) {
    const t = e
    return isNum(t.yaw) && isNum(t.pitch) && isNum(t.roll)
  }
  return !1
}
class CurrentSweepSubFactory {
  currentSweep: string
  sweepTransitionActive: boolean
  currentMode: any
  modeTransitionActive: boolean
  constructor() {
    this.currentSweep = ""
    this.sweepTransitionActive = !1
    this.currentMode = null
    this.modeTransitionActive = !1
  }
  create(e, t, o) {
    return new AggregateSubscription(
      t.onChanged(() => this.throttleSweepDataChanges(e, t)),
      o.onChanged(() => this.throttleViewmodeDataChanges(e, o))
    )
  }
  throttleSweepDataChanges(e, t) {
    const o = t.currentSweep || ""
    const n = t.transition.active && t.transition.to !== t.transition.from
    ;(this.currentSweep === o && this.sweepTransitionActive === n) || ((this.currentSweep = o), (this.sweepTransitionActive = n), e.onChanged())
  }
  throttleViewmodeDataChanges(e, t) {
    const o = t.currentMode,
      n = t.transition.active
    ;(this.currentMode === o && this.modeTransitionActive === n) || ((this.currentMode = o), (this.modeTransitionActive = n), e.onChanged())
  }
}

class CurrentSweepObservable {
  empty: {
    uuid: string
    sid: string
    id: string
    enabled: boolean
    alignmentType: string
    placementType: string
    floorInfo: { id: any; sequence: any }
    neighbors: any[]
    position: { x: number; y: number; z: number }
    rotation: { x: number; y: number; z: number }
  }
  currentSweep: any
  Viewmode: any
  sweepPlacementConverter: any
  constructor(e, t) {
    this.empty = {
      uuid: "",
      sid: "",
      id: "",
      enabled: !1,
      alignmentType: sweepAlignmentEnum.ALIGNED,
      placementType: sweepPlacementEnum.UNPLACED,
      floorInfo: { id: void 0, sequence: void 0 },
      neighbors: [],
      position: { x: 0, y: 0, z: 0 },
      rotation: { x: 0, y: 0, z: 0 }
    }
    this.currentSweep = Object.assign(Object.assign({}, this.empty), {
      floorInfo: Object.assign({}, this.empty.floorInfo),
      neighbors: [],
      position: Object.assign({}, this.empty.position),
      rotation: Object.assign({}, this.empty.rotation)
    })
    this.Viewmode = e.Viewmode
    this.sweepPlacementConverter = t.sweepPlacementConverter
  }
  get data() {
    return this.currentSweep
  }
  equals(e) {
    const t = config.epsilon
    return (
      this.currentSweep.id === e.data.id &&
      this.currentSweep.enabled === e.data.enabled &&
      this.currentSweep.alignmentType === e.data.alignmentType &&
      this.currentSweep.placementType === e.data.placementType &&
      this.currentSweep.floorInfo.id === e.data.floorInfo.id &&
      this.currentSweep.floorInfo.sequence === e.data.floorInfo.sequence &&
      this.compareNeighbors(e.currentSweep) &&
      isSamePoint(this.currentSweep.position, e.data.position, t) &&
      isSamePoint(this.currentSweep.rotation, e.data.rotation, t)
    )
  }
  copy(e) {
    this.copySweep(e.data)
  }
  update(e, t, o, n, r) {
    const a = e.currentSweepObject
    if (!a || e.transition.active || t.transition.active || (!t.isInside() && t.currentMode !== this.Viewmode.Outdoor)) return void this.copySweep(this.empty)
    let s
    try {
      s = o.getFloor(a.floorId || "")
    } catch (e) {
      s = { id: void 0, index: void 0 }
    }
    const i = n.getIdForSweep(a)
    this.currentSweep.uuid = a.uuid
    this.currentSweep.sid = i
    this.currentSweep.id = i
    this.currentSweep.enabled = a.enabled
    this.currentSweep.alignmentType = this.sweepPlacementConverter.toSdkAlignment(a.alignmentType)
    this.currentSweep.placementType = this.sweepPlacementConverter.toSdkPlacement(a.placementType)
    this.currentSweep.neighbors = [...a.neighbours]
    copyPosition(this.currentSweep.position, a.position)
    copyPosition(this.currentSweep.rotation, a.rotation)
    this.currentSweep.alignmentType === sweepAlignmentEnum.UNALIGNED && this.currentSweep.placementType === sweepPlacementEnum.UNPLACED
      ? ((this.currentSweep.floorInfo.id = void 0), (this.currentSweep.floorInfo.sequence = void 0))
      : ((this.currentSweep.floorInfo.id = s.id ? r.getIdFromCwfId(s.id) : s.id), (this.currentSweep.floorInfo.sequence = s.index))
  }
  copySweep(e) {
    this.currentSweep.uuid = e.uuid
    this.currentSweep.sid = e.sid
    this.currentSweep.id = e.id
    this.currentSweep.enabled = e.enabled
    this.currentSweep.alignmentType = e.alignmentType
    this.currentSweep.placementType = e.placementType
    this.currentSweep.floorInfo.id = e.floorInfo.id
    this.currentSweep.floorInfo.sequence = e.floorInfo.sequence
    this.currentSweep.neighbors = [...e.neighbors]
    copyPosition(this.currentSweep.position, e.position), copyPosition(this.currentSweep.rotation, e.rotation)
  }
  compareNeighbors(e) {
    const t = this.currentSweep.neighbors.length
    for (let o = 0; o < t; ++o) if (this.currentSweep.neighbors[o] !== e.neighbors[o]) return !1
    return t === e.neighbors.length
  }
}
class MoveToSweepExecutor {
  NavigateToSweepCommand: any
  issueCommand: any
  cameraTransitionConverter: any
  conversion: any
  constructor(e, t) {
    this.NavigateToSweepCommand = e.NavigateToSweepCommand
    this.issueCommand = t.issueCommand
    this.cameraTransitionConverter = t.cameraTransitionConverter
    this.conversion = t.conversionUtils
  }
  validateInputSweepId(e, t, o) {
    if (!e || !isString(e)) throw Error(`Sweep.moveTo: 'sweep' was not a valid sweep id; got ${e}`)
    const n = o.getSweepForId(e)
    if (!n) throw Error(e + " does not map to a valid sweep in this model")
    return n.id
  }
  validateInput(e, t, o) {
    const n = this.validateInputSweepId(e.sweep, t, o),
      a = e.options || {}
    if (
      a.rotation &&
      !(function (e) {
        const t = e
        return !!t && !("z" in t && !isNum(t.z)) && isNum(t.x) && isNum(t.y)
      })(a.rotation)
    )
      throw Error(`Sweep.moveTo: 'options.rotation' was specified but was not a valid Rotation; got ${a.rotation}`)
    if (a.transition && !Object.values(transitionEnum).includes(a.transition))
      throw Error(`Sweep.moveTo: options.transition was specified but was not a valid transition type; got ${a.transition}`)
    if (a.transitionTime && (!isNum(a.transitionTime) || a.transitionTime <= 0))
      throw Error(`Sweep.moveTo: options.transitionTime was specified but was not a valid, positive time; got ${a.transitionTime}`)
    return {
      sweep: n,
      options: { rotation: a.rotation, transition: a.transition, transitionTime: a.transitionTime || void 0 }
    }
  }
  async exec(e, t, o) {
    const n = e.options.rotation ? this.conversion.rotationToQuaternion(e.options.rotation) : void 0,
      r = e.options.transition,
      a = e.options.transition ? this.cameraTransitionConverter.fromSdkTransition(r) : void 0,
      s = e.options.transitionTime,
      i = await this.issueCommand(new this.NavigateToSweepCommand({ sweep: e.sweep, rotation: n, transition: a, transitionTime: s }))
    t.return(i)
  }
}
class SweepSubFactory {
  create(e, t) {
    return t.onChanged(() => e.onChanged())
  }
}

class SweepDataCollection {
  _data: {}
  conversionUtils: any
  sweepPlacementConverter: any
  constructor(e, t) {
    this._data = {}
    this.conversionUtils = e
    this.sweepPlacementConverter = t
  }
  get data() {
    return this._data
  }
  isItemEqual(e, t) {
    return !deepDiffers(this.data[t], e.data[t])
  }
  update(e, t, o, n) {
    for (const r of e.sweeps()) {
      const { id: e, index: a } = r.floorId ? t.getFloor(r.floorId) : { id: "", index: -1 },
        s = e ? n.getIdFromCwfId(e) : void 0,
        i = o.getIdFromCwfId(r.id),
        d = this.data[i] || {}
      d.uuid = r.uuid
      d.sid = i
      d.id = i
      d.enabled = r.enabled
      d.alignmentType = this.sweepPlacementConverter.toSdkAlignment(r.alignmentType)
      d.neighbors = r.neighbours.map(e => o.getIdFromCwfId(e))
      d.position = copyPosition(r, d.position)
      d.rotation = this.conversionUtils.quaternionToRotation(r.rotation, d.rotation)
      d.floorInfo = s ? { id: s, sequence: a } : { id: "", sequence: -1 }
      this.data[d.id] = d
    }
    for (const e in this.data) {
      o.getSweepForId(e) || delete this.data[e]
    }
  }
  clear() {
    this._data = {}
  }
}
function getSweepIds(e, t, o) {
  if (!Array.isArray(e)) throw Error()
  return e.map(e =>
    (function (e, t, o) {
      if (!isString(e)) throw Error(e + " is not a valid string input")
      const n = o.getSweepForId(e)
      if (!n) throw Error(e + " does not map to a valid sweep in this model")
      return n.id
    })(e, 0, o)
  )
}
function sweepAction(e, t, o, n) {
  n.setVisible(e, t)
  const r = o.getSweep(e)
  r.enabled = t
  r.commit()
}
class EnableExecutor {
  validateInput(e, t, o, n) {
    return { sweepIds: getSweepIds(e.sweepIds, t, n) }
  }
  exec(e, t, o, n) {
    for (const t of e.sweepIds) sweepAction(t, !0, o, n)
    t.return()
  }
}
class DisableExecutor {
  validateInput(e, t, o, n) {
    return { sweepIds: getSweepIds(e.sweepIds, t, n) }
  }
  exec(e, t, o, n) {
    for (const t of e.sweepIds) sweepAction(t, !1, o, n)
    t.return()
  }
}
export default {
  Transition: sdk => sdk.addEnumToInterface({ namespace: "Sweep", name: "Transition", values: transitionEnum }),
  Alignment: sdk => sdk.addEnumToInterface({ namespace: "Sweep", name: "Alignment", values: sweepAlignmentEnum }),
  Placement: sdk => sdk.addEnumToInterface({ namespace: "Sweep", name: "Placement", values: sweepPlacementEnum }),
  current: (sdk, exportOBJ, allFunc, dependencies) => {
    const observable = SdkObservable.create(dependencies, new CurrentSweepSubFactory(), new ClassFactory(CurrentSweepObservable, exportOBJ, allFunc))
    sdk.addObservableToInterface({ namespace: "Sweep", name: "current" }, observable)
  },
  Event: sdk => sdk.addEnumToInterface({ namespace: "Sweep", name: "Placement", values: sweepEventEnum }),
  data: (sdk, allFunc, dependencies) => {
    const data = SdkCollection.create(
      dependencies,
      new SweepSubFactory(),
      new ClassFactory(SweepDataCollection, allFunc.conversionUtils, allFunc.sweepPlacementConverter)
    )
    sdk.addCollectionToInterface({ namespace: "Sweep", name: "data" }, data)
  },
  enable: (sdk, dependencies) => {
    const o = SdkCommand.create(dependencies, new ClassFactory(EnableExecutor))
    sdk.addCommandToInterface({ namespace: "Sweep", name: "enable", args: ["sweepIds"], varArg: !0 }, o)
  },
  disable: (sdk, dependencies) => {
    const n = SdkCommand.create(dependencies, new ClassFactory(DisableExecutor))
    sdk.addCommandToInterface({ namespace: "Sweep", name: "disable", args: ["sweepIds"], varArg: !0 }, n)
  },
  onEvent: (sdk, exportOBJ, allFunc, LegacySweep) => {
    const r = ["", ""]
    const s = ["", ""]
    allFunc.subscribe(exportOBJ.BeginMoveToSweepMessage, async t => {
      const o = await LegacySweep
      t.fromSweep &&
        t.fromSweep !== t.toSweep &&
        ((r[0] = t.fromSweep ? o.getIdFromCwfId(t.fromSweep) : t.fromSweep),
        (r[1] = o.getIdFromCwfId(t.toSweep)),
        sdk.broadcast(sweepEventEnum.EXIT, () => r, t))
    })
    allFunc.subscribe(exportOBJ.BeginSwitchViewmodeMessage, o => {
      o.fromMode === exportOBJ.Viewmode.Panorama &&
        o.toMode !== exportOBJ.Viewmode.Panorama &&
        ((r[0] = s[1]), (r[1] = void 0), sdk.broadcast(sweepEventEnum.EXIT, () => r))
    })
    allFunc.subscribe(exportOBJ.EndMoveToSweepMessage, async t => {
      const o = await LegacySweep
      s[0] = t.fromSweep ? o.getIdFromCwfId(t.fromSweep) : t.fromSweep
      s[1] = o.getIdFromCwfId(t.toSweep)
      sdk.broadcast(sweepEventEnum.ENTER, () => s)
    })
  },
  moveTo: (sdk, exportOBJ, allFunc, dependencies) => {
    const a = SdkCommand.create(dependencies, new ClassFactory(MoveToSweepExecutor, exportOBJ, allFunc))
    sdk.addCommandToInterface({ namespace: "Sweep", name: "moveTo", args: ["sweep", "options"] }, a)
  }
}
